import { _rotate2, _o, _dis, _angleRad, _mid, _disToSeg, _lerp } from "../math/index.js"
import { _mirror } from "../math/mirror.js"
import { sinPoints } from '../algorithm/sin.js'
import { isObject } from "../utils/index.js"
import { controlPoints } from '../algorithm/controlPoints.js'
import { _rough } from './rough.js'
import { _props } from "./canvas.js"
import { kochPoints } from '../algorithm/koch.js'
import { sierpinskiPoints } from '../algorithm/sierpinski.js'
import { _traversal } from "../algorithm/traversal.js"

export const _moveTo = (ctx, p) => {
    ctx.moveTo(p[0], p[1])
}
export const _lineTo = (ctx, p) => {
    ctx.lineTo(p[0], p[1])
}
const _polylineTo = (ctx, points) => {
    points.forEach(t => _lineTo(ctx, t))
}

export const _bezierCurveTo = (ctx, [c1, c2, t]) => {
    ctx.bezierCurveTo(c1[0], c1[1], c2[0], c2[1], t[0], t[1])
}
const _quadraticCurveTo = (ctx, [c, to]) => {
    ctx.quadraticCurveTo(c[0], c[1], to[0], to[1])
}
const _curveTo = (ctx, [from, to], [c1, c2]) => {
    _bezierCurveTo(ctx, [c2, c1, to])
}

const _leftCurveTo = (ctx, [from, to], [c1, c2]) => {
    _bezierCurveTo(ctx, [c1, c2, to])
}

const _doubleCurveTo = (ctx, [from, to], [c1, c2]) => {
    _moveTo(ctx, from)
    _bezierCurveTo(ctx, [c1, c2, to])
    _moveTo(ctx, from)
    _bezierCurveTo(ctx, [c2, c1, to])
}


const _sawtoothTo = (ctx, [from, to], [c1, c2]) => {
    _polylineTo(ctx, [c2, c1, to])
}

const _leftSawtoothTo = (ctx, [from, to], [c1, c2]) => {
    _polylineTo(ctx, [c1, c2, to])
}

const _doubleSawtoothTo = (ctx, [from, to], [c1, c2]) => {
    _moveTo(ctx, from)
    _polylineTo(ctx, [c1, c2, to])
    _moveTo(ctx, from)
    _polylineTo(ctx, [c2, c1, to])
}

const _rightAngleTo = (ctx, [from, to], [c1, c2]) => {
    _polylineTo(ctx, [c2, to])
}
const _leftAngleTo = (ctx, [from, to], [c1, c2]) => {
    _polylineTo(ctx, [c1, to])
}
const _diamondTo = (ctx, [from, to], [c1, c2]) => {
    _moveTo(ctx, from);
    _polylineTo(ctx, [c1, to, c2, from])
}
const _crossTo = (ctx, [from, to], [c1, c2]) => {
    _moveTo(ctx, from);
    _lineTo(ctx, to)
    _moveTo(ctx, c1);
    _lineTo(ctx, c2)
}
const _triangleTo = (ctx, [from, to], [c1, c2]) => {
    _moveTo(ctx, from);
    _polylineTo(ctx, [c2, to, from])

}
const _leftTriangleTo = (ctx, [from, to], [c1, c2]) => {
    _moveTo(ctx, from);
    _polylineTo(ctx, [c1, to, from])
}

const _circleTo = (ctx, [from, to]) => {
    let o = _o([from, to])
    let r = _dis(o, to)
    ctx.beginPath();
    ctx.arc(o[0], o[1], r, 0, 2 * Math.PI);
    ctx.stroke()
}

const _semicircleTo = (ctx, [from, to]) => {
    let o = _o([from, to])
    let r = _dis(o, to)
    let a = _angleRad(from, to)
    ctx.beginPath();
    ctx.arc(o[0], o[1], r, a, Math.PI + a);
    ctx.stroke()
}

const _leftSemicircleTo = (ctx, [from, to]) => {
    let o = _o([from, to])
    let r = _dis(o, to)
    let a = _angleRad(from, to)
    ctx.beginPath();
    ctx.arc(o[0], o[1], r, Math.PI + a, 2 * Math.PI + a);
    ctx.stroke()
}

const _controls = (ctx, points) => {
    let r = 3
    ctx.save()
    _props(ctx, { stroke: 'red', fill: 'gray' })
    points.forEach(t => {
        ctx.arc(t[0], t[1], r, 0, 2 * Math.PI);
    })
    // ctx.stroke()
    // ctx.fill()
    ctx.restore()

}


// 循环
export const _loop = (ctx, { points, loop, step = 1, skew, amplitude, depth = 1, showControls = false }, fn) => {
    let len = points.length
    let prev
    let index = 0
    let callFn = (i, t, next) => {
        if (step > 1) {
            if (i % step === 0) {
                _moveTo(ctx, t)
                prev = t
            } else {
                let [c1, c2] = controlPoints([prev, t], skew, amplitude)
                // if (showControls) {
                //     _controls(ctx, [c1, c2])
                // }
                fn(ctx, [prev, t], [c1, c2], index++)
            }
        } else {
            let [c1, c2] = controlPoints([t, next], skew, amplitude)
            // if (showControls) {
            //     _controls(ctx, [c1, c2])
            // }
            fn(ctx, [t, next], [c1, c2], index++)
        }
    }

    let total = loop ? len : len - 1
    for (let i = 0; i < total; i++) {
        let t = points[i]
        let next = points[(i + 1) % len]
        if (depth === 1) {
            callFn(i, t, next)
        } else {
            let curr = t
            for (let j = 1; j <= depth; j++) {
                let p = _lerp(t, next, j / depth)
                callFn(i, curr, p)
                curr = p
            }
        }
    }
}


// 折线
export const _polyline = (ctx, { points = [], loop, step = 1 }) => {
    _moveTo(ctx, points[0])
    if (step === 1) {
        // 性能优化
        let len = points.length
        for (let i = 1; i < len; i++) {
            let t = points[i]
            _lineTo(ctx, t)
        }
        if (loop) {
            ctx.closePath()
        }

    } else {
        if (step > 1) {
            loop = true
        }
        // for lines
        _loop(ctx, { points, step, loop }, (ctx, ps) => {
            _polylineTo(ctx, ps)
        })
    }
}

// 曲线
export const _bezier = (ctx, options) => {
    let { points } = options
    _moveTo(ctx, points[0])
    _loop(ctx, options, _curveTo)
}

export const _leftBezier = (ctx, options) => {
    let { points } = options
    _moveTo(ctx, points[0])
    _loop(ctx, options, _leftCurveTo)
}
// 双曲线
export const _doubleBezier = (ctx, options) => {
    _loop(ctx, options, _doubleCurveTo)
}
const _symmetry = (ctx, options, fn1, fn2) => {
    _loop(ctx, options, (ctx, [t, next], [c1, c2], index) => {

        _moveTo(ctx, t)
        if (index % 2 === 0) {
            fn1(ctx, [t, next], [c1, c2])
        } else {
            fn2(ctx, [t, next], [c1, c2])
        }
    })
}
const _symmetryBezier = (ctx, options) => {
    _symmetry(ctx, options, _curveTo, _leftCurveTo)
}

// 锯齿
export const _sawtooth = (ctx, options) => {
    let { points } = options
    _moveTo(ctx, points[0])
    _loop(ctx, options, _sawtoothTo)
}

export const _leftSawtooth = (ctx, options) => {
    let { points } = options
    _moveTo(ctx, points[0])
    _loop(ctx, options, _leftSawtoothTo)
}

// 双锯齿
export const _doubleSawtooth = (ctx, options) => {
    _loop(ctx, options, _doubleSawtoothTo)
}

const _symmetrySawtooth = (ctx, options) => {
    _symmetry(ctx, options, _sawtoothTo, _leftSawtoothTo)
}


// 直角拐线
const _rightAngle = (ctx, options) => {
    let { points } = options
    _moveTo(ctx, points[0])
    _loop(ctx, options, _rightAngleTo)

}

const _leftAngle = (ctx, options) => {
    let { points } = options
    _moveTo(ctx, points[0])
    _loop(ctx, options, _leftAngleTo)

}
const _symmetryAngle = (ctx, options) => {
    _symmetry(ctx, options, _rightAngleTo, _leftAngleTo)
}

const _circle = (ctx, options) => {
    _loop(ctx, options, _circleTo)

}

// 菱形
const _diamond = (ctx, options) => {
    _loop(ctx, options, _diamondTo)
}
// 十字
const _cross = (ctx, options) => {
    _loop(ctx, options, _crossTo)
}
// 三角形
const _triangle = (ctx, options) => {
    _loop(ctx, options, _triangleTo)
}

const _leftTriangle = (ctx, options) => {
    _loop(ctx, options, _leftTriangleTo)
}
// 弧形
// arcTo(x1, y1, x2, y2, radius)
// const _arcTo = (ctx, points, lop) => {

// }

// 半圆
const _semicircle = (ctx, options) => {
    _loop(ctx, options, _semicircleTo)
}
const _leftSemicircle = (ctx, options) => {
    _loop(ctx, options, _leftSemicircleTo)

}


// 向心弧线
const _centripetal = (ctx, options) => {
    let { points } = options
    let o = _o(points)
    _moveTo(ctx, points[0])
    _loop(ctx, options, (ctx, ps) => {
        let [t, next] = ps
        _quadraticCurveTo(ctx, [o, next])
    })
}

// 向心弧线
const _leftCentripetal = (ctx, options) => {
    let { points } = options
    let o = _o(points)
    _moveTo(ctx, points[0])
    _loop(ctx, options, (ctx, ps) => {
        let [t, next] = ps
        let c = _mid(t, next)
        let oo = _mirror(o, c)
        return _quadraticCurveTo(ctx, [oo, next])
    })
}
const _doubleCentripetal = (ctx, options) => {
    let { points } = options
    let o = _o(points)
    _loop(ctx, options, (ctx, ps) => {
        let [t, next] = ps
        _moveTo(ctx, t)
        _quadraticCurveTo(ctx, [o, next])
        _moveTo(ctx, t)
        let c = _mid(t, next)
        let oo = _mirror(o, c)
        return _quadraticCurveTo(ctx, [oo, next])
    })
}

// 正玄
const _sin = (ctx, { points, loop }) => {
    let ps = sinPoints(points, {})
    _polyline(ctx, { points: ps, loop })
}

// 互切圆
const _tangentCircle = (ctx, options) => {
    let { points } = options
    let o = _o(points)
    _moveTo(ctx, points[0])
    _loop(ctx, options, (ctx, ps) => {
        let [t, next] = ps
        let c = _mid(t, next)
        let r = _disToSeg(c, [o, next])
        ctx.beginPath();
        ctx.arc(c[0], c[1], r, 0, 2 * Math.PI);
        ctx.stroke()
    })
}


// 杨辉三角
const _sierpinski = (ctx, { points, depth, amplitude, loop, discrete }) => {
    let ps = sierpinskiPoints({ points, depth, amplitude, loop, discrete })
    _traversal({
        ctx,
        points: ps,
        iter: _polylineTo,
        init: _moveTo,
        loop
    })

}
// 杨辉三角贝塞尔
const _sierpinskiBezier = (ctx, { points, depth, amplitude, loop, discrete }) => {
    let ps = sierpinskiPoints({ points, depth, amplitude, loop, discrete })
    let n = 4

    _traversal({
        ctx,
        points: ps,
        n,
        iter: (ctx, [p1, p2, p3, p4]) => {
            return _bezierCurveTo(ctx, [p2, p3, p4])
        },
        init: _moveTo,
        loop
    }
    ).join(' ')
}


const _sierpinskiDiscreteBezier = (ctx, { points, depth, amplitude, loop }) => {
    let ps = sierpinskiPoints({ points, depth, amplitude, loop })
    let n = 4
    let discrete = true
    _traversal({
        ctx,
        points: ps,
        n,
        iter: (ctx, [p1, p2, p3, p4]) => {
            return _moveTo(ctx, p1) + ' ' + _bezierCurveTo(ctx, [p2, p3, p4])
        },
        loop,
        discrete
    }
    )
}

const _koch = (ctx, { points, depth, skew, amplitude, loop, discrete }) => {
    let ps = kochPoints({ points, depth, skew, amplitude, loop, discrete })
    _traversal({
        ctx,
        points: ps,
        iter: _polylineTo,
        init: _moveTo,
        loop
    })
}

const _kochCurve = (ctx, { points, depth, skew, amplitude, loop, discrete }) => {
    let ps = kochPoints({ points, depth, skew, amplitude, loop, discrete })
    let n = 3
    let iter = (ctx, [p1, p2, p3]) => {
        return _quadraticCurveTo(ctx, [p2, p3])
    }
    _traversal({
        ctx,
        points: ps,
        n,
        iter,
        init: _moveTo,
        loop
    })

}

const _kochDiscreteCurve = (ctx, { points, depth, skew, amplitude, loop }) => {
    let ps = kochPoints({ points, depth, skew, amplitude, loop })
    let n = 5
    let iter = (ctx, [p1, p2, p3, p4, p5]) => {
        return _moveTo(ctx, p1) + ' ' + _quadraticCurveTo(ctx, [p2, p3]) + _quadraticCurveTo(ctx, [p4, p5])
    }
    let discrete = true
    _traversal({
        ctx,
        points: ps,
        n,
        iter,
        loop,
        discrete
    })

}



const curveMapFn = {
    bezier: _bezier,
    leftBezier: _leftBezier,
    doubleBezier: _doubleBezier,
    symmetryBezier: _symmetryBezier,
    sawtooth: _sawtooth,
    leftSawtooth: _leftSawtooth,
    doubleSawtooth: _doubleSawtooth,
    symmetrySawtooth: _symmetrySawtooth,
    rightAngle: _rightAngle,
    leftAngle: _leftAngle,
    symmetryAngle: _symmetryAngle,
    circle: _circle,
    diamond: _diamond,
    cross: _cross,
    triangle: _triangle,
    leftTriangle: _leftTriangle,
    semicircle: _semicircle,
    leftSemicircle: _leftSemicircle,
    centripetal: _centripetal,
    leftCentripetal: _leftCentripetal,
    doubleCentripetal: _doubleCentripetal,
    none: _polyline,
    sin: _sin,
    tangentCircle: _tangentCircle,
    rough: _rough,
    sierpinski: _sierpinski,
    sierpinskiBezier: _sierpinskiBezier,
    sierpinskiDiscreteBezier: _sierpinskiDiscreteBezier,
    koch: _koch,
    kochCurve: _kochCurve,
    kochDiscreteCurve: _kochDiscreteCurve
}


// 曲线
export const _curve = (ctx, { points, curve, loop = false, step = 1, discrete }) => {
    let type,
        amplitude = 1,
        skew = 0,
        depth = 1,
        showControls = false
    if (isObject(curve)) {
        type = curve.type
        amplitude = curve.amplitude
        skew = curve.skew
        depth = curve.depth
        showControls = curve.showControls
    } else {
        type = curve
    }
    let fn = curveMapFn[type] || _polyline
    if (step > 1) {
        loop = true
    }

    fn(ctx, { points, loop, step, skew, amplitude, depth, showControls, discrete })
}